package bigoo.peer;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import bigoo.peer.PublisherSource.RideRequest;
import bigoo.util.Config;
import eduni.simjava.Sim_entity;
import eduni.simjava.Sim_event;
import eduni.simjava.Sim_port;
import eduni.simjava.Sim_stat;
import eduni.simjava.Sim_system;

public class BigooPeer extends Sim_entity {

    public static final int LOCAL_PUBLISH_EVENT = 1;
    public static final int REMOTE_PUBLISH_EVENT = 2;
    public static final int LOCAL_SEARCH_EVENT = 3;
    public static final int EPIDEMIC_PROPAGATION_EVENT = 4;

    public enum PropagationProtocol {
	EPIDEMIC, FLOODING;
    }

    private final Map<String, PeerInfo> neighbors = new HashMap<String, PeerInfo>(); // peerIDxpeerInfo
    private final String myPeerID;
    private RideManager rideMng = null;
    private final Sim_port in;
    private final PropagationProtocol protocol;
    private final double netDelay;
    private final Sim_stat stat;
    private final double processDelay;
    private double before;
    private boolean isStable;

    public BigooPeer(String peerId, PropagationProtocol protocol) {
	super(peerId);
	this.protocol = protocol;
	this.netDelay = Double.parseDouble(Config.getProperty(Config.NETWORK_DELAY));
	this.processDelay = Double.parseDouble(Config.getProperty(Config.PROCESS_DELAY));

	this.myPeerID = peerId;

	this.stat = new Sim_stat();
	this.stat.add_measure(Sim_stat.THROUGHPUT);
	this.stat.add_measure(Sim_stat.ARRIVAL_RATE);
	this.stat.add_measure(Sim_stat.SERVICE_TIME);
	this.stat.add_measure(Sim_stat.UTILISATION);
	this.set_stat(this.stat);

	// Nossas Métricas
	this.stat.add_measure("Received msgs", Sim_stat.RATE_BASED);
	this.stat.add_measure("Search msgs", Sim_stat.RATE_BASED);
	this.stat.add_measure("Successful search msgs", Sim_stat.RATE_BASED);
	this.stat.add_measure("Wasted msgs", Sim_stat.RATE_BASED);
	// TODO pensar num nome
	this.stat.add_measure("State", Sim_stat.STATE_BASED, false);
	this.stat.calc_proportions("State", new double[] { 0, 1 });

	this.in = new Sim_port(this.myPeerID + "in");
	this.add_port(this.in);
    }

    public String getPeerID() {
	return this.myPeerID;
    }

    @Override
    public void body() {
	this.before = Sim_system.clock();
	this.rideMng = new RideManager();
	this.isStable = false;
	while (Sim_system.running()) {
	    Sim_event e = new Sim_event();
	    this.sim_get_next(e);
	    RideRequest req;
	    this.sim_process(this.processDelay);
	    switch (e.get_tag()) {
	    case LOCAL_PUBLISH_EVENT:
		req = (RideRequest) e.get_data();
		this.localPublishRide(req.getDest(), req.getPlaces(), req.getDate());
		break;
	    case REMOTE_PUBLISH_EVENT:
		PublishRideMessage msg = (PublishRideMessage) e.get_data();
		this.remotePublishRide(msg);
		break;
	    case LOCAL_SEARCH_EVENT:
		this.stat.update("Search msgs", Sim_system.clock());
		req = (RideRequest) e.get_data();
		for (Ride ride : this.rideMng.getAvailableRiders()) {
		    if (ride.getDestination() == req.getDest() && ride.getTimestamp() == req.getDate()) {
			this.stat.update("Successful search msgs", Sim_system.clock());
			break;
		    }
		}
		break;
	    case EPIDEMIC_PROPAGATION_EVENT:
		this.doEpidemic();
		break;
	    }
	    this.sim_completed(e);
	    double now = Sim_system.sim_clock();
	    this.stat.update("State", this.isStable ? 1 : 2, this.before, now);
	    this.before = now;
	}
    }

    public void peerIsUp(String peerId) {
	if (!this.neighbors.containsKey(peerId)) {
	    this.sim_trace(1, "New neighbor peer \'" + peerId + "\' is up.");
	    Sim_port out = new Sim_port(this.myPeerID + peerId + "out");
	    this.add_port(out);
	    PeerInfo newPeer = new PeerInfo(peerId, out);
	    this.neighbors.put(peerId, newPeer);
	}
    }

    public void localPublishRide(int destination, int places, int date) {
	String rideID = this.rideMng.registerRide(this.myPeerID, destination, places, date);

	if (rideID != null && this.protocol == PropagationProtocol.FLOODING) {
	    this.doFlooding(this.myPeerID);
	}
    }

    /**
     * Processa
     */
    private void remotePublishRide(PublishRideMessage m) {
	this.stat.update("Received msgs", Sim_system.clock());
	String peerID = m.getSenderID();
	this.sim_trace(1, "PUBLISH_RIDE message received from " + peerID);

	int merged = this.rideMng.mergeRides(m.getRides());
	if (merged == 0) {
	    this.stat.update("Wasted msgs", Sim_system.clock());
	    this.isStable = true;
	} else {
	    this.isStable = false;
	}
	if (this.protocol == PropagationProtocol.FLOODING && merged != 0) {
	    this.doFlooding(m.getSenderID());
	}
    }

    private boolean sendMessage(PublishRideMessage message, PeerInfo peer) {
	this.sim_schedule(peer.getPort(), this.netDelay, REMOTE_PUBLISH_EVENT, message);
	return true;
    }

    private void doFlooding(String sourceID) {
	for (String peerID : this.neighbors.keySet()) {
	    if (!peerID.equals(sourceID)) {
		PeerInfo peer = this.neighbors.get(peerID);
		this.sendMessage(new PublishRideMessage(this.myPeerID, this.rideMng.getSomeRides(peerID)), peer);
	    }
	}
    }

    private void doEpidemic() {
	if (this.neighbors.size() > 0) {
	    PeerInfo peer = (PeerInfo) this.neighbors.values().toArray()[(int) Math.round(Math.random() * (this.neighbors.size() - 1))];
	    Collection<Ride> rides = this.rideMng.getSomeRides(peer.getPeerID());
	    PublishRideMessage message = new PublishRideMessage(this.myPeerID, rides);
	    this.sim_trace(1, "Sending rides to " + peer.getPeerID());
	    this.sendMessage(message, peer);
	}
    }

}
